/**
 * Copyright (c) [2022] [yanyeori]
 * [yanyeori-framework] is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package com.yanyeori.utilscollection;

import com.yanyeori.framework.core.util.IdUtil;
import com.yanyeori.framework.core.util.JacksonUtil;
import io.jsonwebtoken.*;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author chenkuan
 * @version v1.0
 * @desc Jwt Token工具类
 * @date 2020/11/20
 */
public class JwtUtil {
    //claim存储信息key
    private static final String CLAIM_KEY_USER_DETAIL = "user_detail";
    private static final String CLAIM_KEY_AUTHORITIES = "authorities";
    private static final String CLAIM_KEY_MARK_TOKEN = "token_mark";

    //token加密算法
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    //记录登陆用户在线数
    private static final Map<String, LinkedList<UserOnline>> USER_ONLINE_MAP = new ConcurrentHashMap<>(32);

    /**
     * 用户数据
     */
    public static class UserDetail {
        private String username;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public Collection<String> getAuthorities() {
            return null;
        }
    }

    /**
     * 用户登录信息对象
     */
    public static class UserOnline {
        public String ip;
        public Date createTime;
        public Date expirationTime;
        public boolean isOnline;

        public UserOnline() {
        }

        public UserOnline(String ip) {
            this.ip = ip;
            this.createTime = new Date();
            this.expirationTime = generateExpirationDate(60 * 60);
            this.isOnline = true;
        }
    }

    /**
     * 从token摘要中获取UserDetail
     */
    public static UserDetail getUserFromToken(String token, boolean expiredClaim) {
        UserDetail userDetail;
        try {
            final Claims claims = getClaimsFromToken(token, expiredClaim);
            if (claims == null || claims.get(CLAIM_KEY_USER_DETAIL) == null) {
                return null;
            }
            userDetail = JacksonUtil.parseObject(claims.get(CLAIM_KEY_USER_DETAIL).toString(), UserDetail.class);
        } catch (Exception e) {
            userDetail = null;
        }
        return userDetail;
    }

    /**
     * 从token中获取用户名
     */
    public static String getUsernameFromToken(String token, boolean expiredClaim) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token, expiredClaim);
            username = claims != null ? claims.getSubject() : null;
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 从token摘要中获取用户名
     */
    public static String getUsernameFromTokenMark(String tokenMark) {
        String username;
        try {
            final Claims claims = getClaimsFromTokenMark(tokenMark);
            username = claims != null ? claims.getSubject() : null;
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 从token摘要中获取原token
     */
    public static String getTokenFromTokenMark(String tokenMark) {
        String token;
        try {
            final Claims claims = getClaimsFromTokenMark(tokenMark);
            if (claims == null || claims.get(CLAIM_KEY_MARK_TOKEN) == null) {
                return null;
            }
            token = claims.get(CLAIM_KEY_MARK_TOKEN).toString();
        } catch (Exception e) {
            token = null;
        }
        return token;
    }

    /**
     * 从token中获取创建时间
     */
    private static Date getCreatedDateFromToken(String token, boolean expiredClaim) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token, expiredClaim);
            created = claims != null ? claims.getIssuedAt() : null;
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 从token中获取过期时间
     */
    private static Date getExpirationDateFromToken(String token, boolean expiredClaim) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token, expiredClaim);
            expiration = claims != null ? claims.getExpiration() : null;
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 从token摘要中获取过期时间
     */
    private static Date getExpirationDateFromTokenMark(String tokenMark) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromTokenMark(tokenMark);
            expiration = claims != null ? claims.getExpiration() : null;
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 登录时验证token
     */
    public static Boolean validateToken(String token, String username, UserDetail userDetail) {
        return !isTokenExpired(token) && userDetail.getUsername().equals(username);
    }

    /**
     * token是否过期
     */
    public static Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token, false);
        return expiration == null || expiration.before(new Date());
    }

    /**
     * token摘要是否过期
     */
    public static Boolean isTokenMarkExpired(String tokenMark) {
        final Date expiration = getExpirationDateFromTokenMark(tokenMark);
        return expiration == null || expiration.before(new Date());
    }

    /**
     * 生成包含UserDetail的token
     */
    public static String generateAccessToken(UserDetail userDetail) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(CLAIM_KEY_USER_DETAIL, JacksonUtil.toJson(userDetail));
        claims.put(CLAIM_KEY_AUTHORITIES, JacksonUtil.toJson(authoritiesToList(userDetail.getAuthorities())));
        return generateAccessToken(userDetail.getUsername(), claims);
    }

    /**
     * 生成包含原token的token摘要
     */
    public static String generateAccessTokenMark(String token, UserDetail userDetail) {
        Map<String, Object> claims = new HashMap<>(16);
        claims.put(CLAIM_KEY_MARK_TOKEN, token);
        return generateAccessTokenMark(userDetail.getUsername(), claims);
    }

    /**
     * 生成token
     */
    private static String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, 60 * 60, "abc");
    }

    /**
     * 生成token摘要
     */
    private static String generateAccessTokenMark(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, 60 * 60, "abc");
    }

    private static String generateToken(String subject, Map<String, Object> claims, long expiration, String secret) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(IdUtil.randomUUID())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

    /**
     * 从token中获取Claims
     */
    private static Claims getClaimsFromToken(String token, boolean expiredClaim) {
        return getClaimsFromToken(token, "abc", expiredClaim);
    }

    /**
     * 从token摘要中获取Claims
     */
    private static Claims getClaimsFromTokenMark(String tokenMark) {
        return getClaimsFromToken(tokenMark, "abc", false);
    }

    private static Claims getClaimsFromToken(String token, String secret, boolean expiredClaim) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (ExpiredJwtException e) {
            try {
                claims = expiredClaim ? e.getClaims() : null;
            } catch (Exception e1) {
                claims = null;
            }
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 根据token过期时长计算过期的时间
     */
    private static Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiration));
    }

    private static List<String> authoritiesToList(Collection<String> authorities) {
        List<String> list = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(authorities)) {
            list.addAll(authorities);
        }
        return list;
    }

    /*用户登陆在线ip列表维护*/

    /**
     * 登陆，维护map
     *
     * @param userId  用户id
     * @param loginIp ip地址
     */
    public static UserOnline loginIp(String userId, String loginIp) {
        removeIp(userId, loginIp);
        UserOnline userOnline = new UserOnline(loginIp);
        USER_ONLINE_MAP.computeIfAbsent(userId, x -> new LinkedList<>()).addFirst(userOnline);
        return userOnline;
    }

    /**
     * 登陆，维护map
     *
     * @param userId     用户id
     * @param userOnline 用户在线信息
     */
    public static void loginIp(String userId, UserOnline userOnline) {
        removeIp(userId, userOnline.ip);
        USER_ONLINE_MAP.computeIfAbsent(userId, x -> new LinkedList<>()).addFirst(userOnline);
    }

    /**
     * 登出用户的最旧ip
     *
     * @param userId 用户id
     */
    public static UserOnline logoutLast(String userId) {
        return USER_ONLINE_MAP.get(userId).removeLast();
    }

    /**
     * 登出用户的指定ip
     *
     * @param userId  用户id
     * @param loginIp ip地址
     */
    public static void logoutIp(String userId, String loginIp) {
        removeIp(userId, loginIp);
        if (onlineNum(userId) == 0) {
            USER_ONLINE_MAP.remove(userId);
        }
    }

    private static void removeIp(String userId, String loginIp) {
        LinkedList<UserOnline> userOnlines = USER_ONLINE_MAP.get(userId);
        if (userOnlines != null) {
            Iterator<UserOnline> iterator = userOnlines.iterator();
            while (iterator.hasNext()) {
                UserOnline userOnline = iterator.next();
                if (userOnline.ip.equals(loginIp)) {
                    iterator.remove();
                    break;
                }
            }
        }
    }

    /**
     * 获取用户的在线数
     *
     * @param userId 用户id
     */
    public static int onlineNum(String userId) {
        if (!USER_ONLINE_MAP.containsKey(userId) || USER_ONLINE_MAP.get(userId) == null) {
            return 0;
        }
        return USER_ONLINE_MAP.get(userId).size();
    }

    public static Map<String, LinkedList<UserOnline>> getUserOnlineMap() {
        return USER_ONLINE_MAP;
    }
}
